package com.kaluli.project.service;

import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.IOException;

/**
 * Created by niwei on 2016-03-26.
 */
@Service
public class RestClient {

    private static final Logger logger = LoggerFactory.getLogger(RestClient.class);

    private static final String HEADER_NAME_CONTENT_TYPE = "Content-type";

    private static final String HEADER_VALUE_JSON_UTF8 = "application/json;charset=utf-8";
    
    private static final String HEADER_VALUE_FORM="application/x-www-form-urlencoded;charset=UTF-8";

    private CloseableHttpClient client;

    public RestClient() {
        init();
    }

    private void init() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(5000);
        connectionManager.setDefaultMaxPerRoute(5000);
        connectionManager.setValidateAfterInactivity(100);

        RequestConfig globalConfig = RequestConfig.custom()
                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                .setConnectTimeout(15000)
                .setSocketTimeout(30000)
                .setExpectContinueEnabled(false)
                //.setProxy(new HttpHost("127.0.0.1", 28888))
                .build();

        client = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(globalConfig)
                .build();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public <T> T get(String url, Class<T> clazz) throws IOException {
        HttpGet httpget = new HttpGet(url);
        return send(httpget, clazz);
    }

    public <T> T get(String url, TypeReference<T> valueTypeRef) throws IOException {
        HttpGet httpget = new HttpGet(url);
        return send(httpget, valueTypeRef);
    }

    public String get(String url) throws IOException {
    	HttpGet httpget = new HttpGet(url);
        return send(httpget);
    }
    
    public String post(String url) throws IOException {
        HttpPost post = new HttpPost(url);
        composeRequest(post,null);
        return send(post);
    }
    
    public String formPost(String url) throws IOException {
        HttpPost post = new HttpPost(url);
        composeFormRequest(post,null);
        return send(post);
    }
    
    public String post(String url,Object data) throws IOException {
        HttpPost post = new HttpPost(url);
        composeRequest(post,data);
        return send(post);
    }
    
    
    public <T> T post(String url, Object data, Class<T> clazz) throws IOException {
        HttpPost post = new HttpPost(url);
        composeRequest(post, data);
        return send(post, clazz);
    }

    public <T> T post(String url, Object data, TypeReference<T> valueTypeRef) throws IOException {
        HttpPost post = new HttpPost(url);
        composeRequest(post, data);
        return send(post, valueTypeRef);
    }

    private void composeRequest(HttpEntityEnclosingRequestBase request, Object data) throws IOException {
        if (data != null) {
            request.setHeader(HEADER_NAME_CONTENT_TYPE, HEADER_VALUE_JSON_UTF8);
            ObjectMapper mapper = new ObjectMapper();
            String bodyString = mapper.writeValueAsString(data);
            StringEntity body = new StringEntity(bodyString, ContentType.APPLICATION_JSON);
            request.setEntity(body);
            logger.debug("Request body: {}", bodyString);
        }else{
        	request.setHeader(HEADER_NAME_CONTENT_TYPE, HEADER_VALUE_JSON_UTF8);
        }
    }
    
    private void composeFormRequest(HttpEntityEnclosingRequestBase request, Object data) throws IOException {
        if (data != null) {
            request.setHeader(HEADER_NAME_CONTENT_TYPE, HEADER_VALUE_FORM);
            ObjectMapper mapper = new ObjectMapper();
            String bodyString = mapper.writeValueAsString(data);
            StringEntity body = new StringEntity(bodyString, ContentType.APPLICATION_JSON);
            request.setEntity(body);
            logger.debug("Request body: {}", bodyString);
        }else{
        	request.setHeader(HEADER_NAME_CONTENT_TYPE, HEADER_VALUE_FORM);
        }
    }

    public <T> T put(String url, Object data, Class<T> clazz) throws IOException {
        HttpPut put = new HttpPut(url);
        composeRequest(put, data);
        return send(put, clazz);
    }

    public <T> T patch(String url, Object data, Class<T> clazz) throws IOException {
        HttpPatch patch = new HttpPatch(url);
        composeRequest(patch, data);
        return send(patch, clazz);
    }

    public <T> T delete(String url, Class<T> clazz) throws IOException {
        HttpDelete delete = new HttpDelete(url);
        return send(delete, clazz);
    }

    private <T> T send(HttpUriRequest request, Class<T> clazz) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
        return mapper.readValue(getResponseBody(request), clazz);
    }
    
    private String send(HttpUriRequest request) throws IOException {
        return getResponseBody(request);
    }

    private <T> T send(HttpUriRequest request, TypeReference<T> valueTypeRef) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(getResponseBody(request), valueTypeRef);
    }

    private String getResponseBody(HttpUriRequest request) throws IOException {
        logger.debug("Request URL: {} {}", request.getMethod(), request.getURI());
        for (Header header : request.getAllHeaders()) {
            logger.debug("Request headers: {} {}", header.getName(), header.getValue());
        }
        try (CloseableHttpResponse response = client.execute(request)) {
            int statusCode = response.getStatusLine().getStatusCode();
            logger.debug("Response code: {}", statusCode);
            String body = convertResponseBody(response.getEntity());
            logger.debug("Response body: {}", body);
            if (HttpStatus.SC_OK != statusCode) {
                throw new IOException(body);
            }
            return body;
        }
    }

    private String convertResponseBody(HttpEntity entity) throws IOException {
        if (entity == null) {
            return null;
        }
        Header contentEncodingHeader = entity.getContentEncoding();
        String contentEncoding = null;
        if (contentEncodingHeader != null) {
            contentEncoding = contentEncodingHeader.getValue();
        }

        if ("gzip".equals(contentEncoding)) {
            return EntityUtils.toString(new GzipDecompressingEntity(entity));
        } else if ("deflate".equals(contentEncoding)) {
            return EntityUtils.toString(new DeflateDecompressingEntity(entity));
        } else {
        	return new String(EntityUtils.toString(entity).getBytes("ISO_8859_1"),"UTF-8");
        }
    }


}
